/*-------------------------------------------------------------------------
 * drawElements Quality Program Test Executor
 * ------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Cross-thread function call dispatcher.
 *//*--------------------------------------------------------------------*/

#include "xeCallQueue.hpp"
#include "deInt32.h"
#include "deMemory.h"

using std::vector;

static inline int getNextQueueSize (int curSize, int minNewSize)
{
	return de::max(curSize*2, 1<<deLog2Ceil32(minNewSize));
}

namespace xe
{

// CallQueue

CallQueue::CallQueue (void)
	: m_callSem		(0)
	, m_callQueue	(64)
{
}

CallQueue::~CallQueue (void)
{
	// Destroy all calls.
	for (vector<Call*>::iterator i = m_calls.begin(); i != m_calls.end(); i++)
		delete *i;
}

void CallQueue::callNext (void)
{
	Call* call = DE_NULL;

	// Wait for a call.
	m_callSem.decrement();

	// Acquire call from buffer.
	{
		de::ScopedLock lock(m_lock);
		call = m_callQueue.popBack();
	}

	try
	{
		// \note Enqueue lock is not held during call so it is possible to enqueue more work from dispatched call.
		call->getFunction()(CallReader(call));
		call->clear();
	}
	catch (const std::exception&)
	{
		try
		{
			// Try to push call into free calls list.
			de::ScopedLock lock(m_lock);
			m_freeCalls.push_back(call);
		}
		catch (const std::exception&)
		{
			// We can't do anything but ignore this.
		}

		throw;
	}

	// Push back to free calls list.
	{
		de::ScopedLock lock(m_lock);
		m_freeCalls.push_back(call);
	}
}

Call* CallQueue::getEmptyCall (void)
{
	de::ScopedLock	lock	(m_lock);
	Call*			call	= DE_NULL;

	// Try to get from free calls list.
	if (!m_freeCalls.empty())
	{
		call = m_freeCalls.back();
		m_freeCalls.pop_back();
	}

	// If no free calls were available, create a new.
	if (!call)
	{
		m_calls.reserve(m_calls.size()+1);
		call = new Call();
		m_calls.push_back(call);
	}

	return call;
}

void CallQueue::enqueue (Call* call)
{
	de::ScopedLock lock(m_lock);

	if (m_callQueue.getNumFree() == 0)
	{
		// Call queue must be grown.
		m_callQueue.resize(getNextQueueSize(m_callQueue.getSize(), m_callQueue.getSize()+1));
	}

	m_callQueue.pushFront(call);
	m_callSem.increment();
}

void CallQueue::freeCall (Call* call)
{
	de::ScopedLock lock(m_lock);
	m_freeCalls.push_back(call);
}

// Call

Call::Call (void)
	: m_func(DE_NULL)
{
}

Call::~Call (void)
{
}

void Call::clear (void)
{
	m_func = DE_NULL;
	m_data.clear();
}

// CallReader

CallReader::CallReader (Call* call)
	: m_call	(call)
	, m_curPos	(0)
{
}

void CallReader::read (deUint8* bytes, int numBytes)
{
	DE_ASSERT(m_curPos + numBytes <= m_call->getDataSize());
	deMemcpy(bytes, m_call->getData()+m_curPos, numBytes);
	m_curPos += numBytes;
}

const deUint8* CallReader::getDataBlock (int numBytes)
{
	DE_ASSERT(m_curPos + numBytes <= m_call->getDataSize());

	const deUint8* ptr = m_call->getData()+m_curPos;
	m_curPos += numBytes;

	return ptr;
}

CallReader& operator>> (CallReader& reader, std::string& value)
{
	value.clear();
	for (;;)
	{
		char c;
		reader.read((deUint8*)&c, sizeof(char));
		if (c != 0)
			value.push_back(c);
		else
			break;
	}

	return reader;
}

// CallWriter

CallWriter::CallWriter (CallQueue* queue, Call::Function function)
	: m_queue		(queue)
	, m_call		(queue->getEmptyCall())
	, m_enqueued	(false)
{
	m_call->setFunction(function);
}

CallWriter::~CallWriter (void)
{
	if (!m_enqueued)
		m_queue->freeCall(m_call);
}

void CallWriter::write (const deUint8* bytes, int numBytes)
{
	DE_ASSERT(!m_enqueued);
	int curPos = m_call->getDataSize();
	m_call->setDataSize(curPos+numBytes);
	deMemcpy(m_call->getData()+curPos, bytes, numBytes);
}

void CallWriter::enqueue (void)
{
	DE_ASSERT(!m_enqueued);
	m_queue->enqueue(m_call);
	m_enqueued = true;
}

CallWriter& operator<< (CallWriter& writer, const char* str)
{
	int pos = 0;
	for (;;)
	{
		writer.write((const deUint8*)str + pos, sizeof(char));
		if (str[pos] == 0)
			break;
		pos += 1;
	}

	return writer;
}

} // xe
